Array.prototype.sum = function(){
    var acc = 0
    for(var i = 0; i<this.length; i++){
        acc += this[i]*1
    }
    return acc
}

Array.prototype.min = function(){
    return Math.min.apply(null, this)
}

Array.prototype.map = function(cback){
    var result = new Array()
    for(var i = 0; i<this.length; i++){
        result.push(cback(this[i]))
    }
    return result
}

Array.prototype.grep = function(cback){
    var result = new Array()
    for(var i = 0; i<this.length; i++){
        if(cback(this[i])){
            result.push(this[i])
        }
    }
    return result
}

Array.prototype.unique = function() {
    var isEqual = function(f, s){
        var nrKeys = function(a) {
            var i = 0;
            for (key in a) {
                i++;
            }
            return i;
        }
        var compareAssociativeArrays = function(a, b) {
            if (a == b) {
                return true;
            }   
            if (nrKeys(a) != nrKeys(b)) {
                return false;
            }
            for (key in a) {     
                if (a[key] != b[key]) {
                    return false;
                }
            }
            return true;
        }
        return compareAssociativeArrays(f, s)
    }
    var a = [];
    var l = this.length;
    for(var i=0; i<l; i++) {
        for(var j=i+1; j<l; j++) {
            // If this[i] is found later in the array
            if (isEqual(this[i], this[j]))
                j = ++i;
        }
        a.push(this[i]);
    }
    return a;
}

Array.prototype.subList = function(min, max){
    var res = new Array()
    for(var i = min; i <= max; i++){
        res.push(this[i])
    }
    return res
}

Array.prototype.check = function(num){
    for(var i = 0; i <= this.length; i++){
        if (this[i] == num) {
            return true;
        }
    }
    return false;
}

$(document).ready(function(){

    $('#calcular').click(function(){
        matrizes = getMatrizes();
        if (!matrizes.length) {
            myAlert('Nenhuma Matriz foi informada');
            return false;
        }
        cortes = getCortes();
        
        console.log(cortes)
        
        if (!cortes.length) {
            myAlert('Nenhum Corte foi informado');
            return false;
        }

        for (i = 0; i < matrizes.length; i++) {
            if(!validarMatrizesCortes(matrizes[i], cortes)){
                return false;
            }
        }

        for (i = 0; i < matrizes.length; i++) {
            cortesPossiveis = gerarCortes(matrizes[i], cortes);
        }
        
        imprimirCortes(cortes);
        imprimirVariaveis(cortesPossiveis);
        associarCortesEPossibilidades(cortes, cortesPossiveis);
        imprimirFuncaoObjetiva(cortesPossiveis);
        imprimirRestricoes(cortes);
        
    });
    
    /**
     * Distribuir tipos de cortes entre os cortes afetados
     */
    function associarCortesEPossibilidades(cortes, possibilidades) {
        
        for (i = 0; i < cortes.length; i++) {
            cortes[i].possibilidades = new Array();
            for (j = 0; j < possibilidades.length; j++) {
                if (possibilidades[j].check(cortes[i].tamanho)) {
                    indice = j+1
                    cortes[i].possibilidades.push('x' + indice);
                }
            }
        }
    }
    
    /**
     * Função para imprimir Função Objetiva
     */
    function imprimirRestricoes(cortes) {
        strModeloMatematico = '/* Variable bounds */\r\n';;
                              
        for (i = 0; i < cortes.length; i++) {
            strModeloMatematico += '   '
            for (j = 0; j < cortes[i].possibilidades.length; j++) {
                strModeloMatematico += cortes[i].possibilidades[j]
                if (j != (cortes[i].possibilidades.length - 1)) {
                    strModeloMatematico += ' + ';
                }
            }
            strModeloMatematico += ' >= ' + cortes[i].minimo + ';\r\n';
        }
        
        $('#saida').html($('#saida').html() + strModeloMatematico);
    }
    
    /**
     * Função para imprimir Função Objetiva
     */
    function imprimirFuncaoObjetiva(possibilidades) {
        strModeloMatematico = '-----------------MODELO MATEMÁTIVO------------------\r\n';
        strModeloMatematico += '/* Objective function */\r\n';
                              
        strModeloMatematico += 'min: ';
        for (i = 0; i < possibilidades.length; i++) {
            strModeloMatematico += 'x'+ (i+1);
            if (i != (possibilidades.length - 1)) {
                strModeloMatematico += ' + ';
            }
        }
        strModeloMatematico += ';\r\n';
        $('#saida').html($('#saida').html() + strModeloMatematico);
    }

    /**
     * Função para imprimir os cortes
     */
    function imprimirCortes(cortes) {
        strCortes = '-----------------------CORTES-----------------------\r\n'
        for (i = 0; i < cortes.length; i++) {
            strCortes += cortes[i].nome + '  - ' + cortes[i].tamanho + '\r\n';
        }
        $('#saida').html(strCortes);
    }
    
    /**
     * Função para imprimir variáveis
     */
    function imprimirVariaveis(possibilidades) {
        strVariáveis = '---------------------VARIÁVEIS----------------------\r\n'
        for (i = 0; i < possibilidades.length; i++) {
            indice = i + 1;
            strVariáveis += 'x'+ indice + '  - ' + possibilidades[i] +'\r\n';
        }
        $('#saida').html($('#saida').html() + strVariáveis);
    }

    /**
     * Função geradora de cortes
     */
    function gerarCortes(matriz, cortes) {
        var max = matriz.tamanho;
        var myCortes = new Array();
        for (i = 0; i < cortes.length; i++) {
            myCortes.push(cortes[i].tamanho * 1);
        }
        var possibilidades = geraPossibilidades(max, myCortes);
        possibilidades.sort(multi)
        for(i = 0; i < possibilidades.length; i++) {
            possibilidades[i].sort(function(a, b){return b-a})
        }
        for(i = 0; i < possibilidades.length; i++) {
            for (j = 0; j < possibilidades[i].length; j++) {
                possibilidades[i][j] = possibilidades[i][j]*1
            }
        }
        possibilidades.sort(multi)
        return possibilidades;
    }

    /**
     * Função para validar os tamanhos de cortes e matrizes
     */
    function validarMatrizCortes(matriz, arrayCortes) {
        for (i = 0; i < arrayCortes.length; i++) {
            if (matriz.tamanho*1 < arrayCortes[i].tamanho*1) {
                return false;
            }
        }
        return true
    }

    /**
     * Função para localizar cortes duplicados
     */
    function validaCortes(arrayCortes) {
        for (i = 0; i < arrayCortes.length; i++) {
            for (j = i+1; j < arrayCortes.length; j++) {
                if (arrayCortes[i].tamanho == arrayCortes[j].tamanho) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Função validadora de matrizes e cortes
     */
    function validarMatrizesCortes(matriz, arrayCortes) {
        if(!validarMatrizCortes(matriz, arrayCortes)){
            myAlert('Existem cortes maiores que matrizes em sua formulação, favor verificar.');
            return false;
        }
        if(!validaCortes(arrayCortes)){
            myAlert('Existem cortes duplicados (Mesmo tamanho) em sua formulação, favor verificar.');
            return false;
        }
        return true;
    }


    /**
     * Coletar Matrizes informadas Ordenadas por Tamanho
     */
    function getMatrizes() {
        var matrizes = new Array();
        var inputs = $('#matrizes').find('input');
        for (i = 0; i < inputs.length; i = i+4) {
            matriz = {
                'tamanho' : $(inputs[i]).val(),
                'minimo'  : $(inputs[i+1]).val(),
                'maximo'  : $(inputs[i+2]).val(),
                'custo'   : $(inputs[i+3]).val()
            }
            matrizes.push(matriz);
        }
        matrizes.sort(porTamanho)
        for (i = 0; i < matrizes.length; i++) {
            indice = i+1;
            matrizes[i].nome = 'M' + indice;
        }
        return matrizes;
    }

    /**
     * Coletar Campos informados Ordenadas por Tamanho
     */
    function getCortes() {
        var cortes = new Array();
        var inputs = $('#cortes').find('input');

        //TODO: Função alterada para reduzir a complexidade
        for (i = 0; i < inputs.length; i = i+2) {
            corte = {
                'tamanho' : $(inputs[i]).val(),
                'minimo'  : $(inputs[i+1]).val(),
                //'maximo'  : $(inputs[i+2]).val(),
                //'exato'   : $(inputs[i+3]).val()
            }
            cortes.push(corte);
        }
        cortes.sort(porTamanho)
        for (i = 0; i < cortes.length; i++) {
            indice = i+1;
            cortes[i].nome = 'c' + indice;
        }
        return cortes;
    }


    /**
     * Função auxiliar para ordenar por tamanho
     */
    function porTamanho(a, b) {
        return a.tamanho < b.tamanho;
    }
    
    function multi(a, b) {
        lenA = a.length;
        lenB = b.length;
        var j = lenA < lenB ? lenA : lenB;
        for (i = 0; i < j; i++) {
            return a[i] < b[i];
        }
        return lenA < lenB;
    }


    /***********************************************************/
    
    function checaCortes(maximum, elements, poss){
        var inner = function(max, cortes, possibilidade, i){
            var aux = new Array()
            if(possibilidade.sum() > max) return aux
            aux = aux.concat(possibilidade)
            var sum = (possibilidade.length != 0) ? possibilidade.sum() : 0
            while(sum < max){
                if((sum + cortes[i]) <= max){
                    aux.push(cortes[i])	
                } 
                sum += cortes[i]
            }
            return aux.sort()
        }
        var result = new Array()
        for(var i = 0; i < cortes.length; i++){
            result.push(inner(maximum, elements, poss, i))
        }
        return result
    }

    function geraPossibilidades(max, cortes){
        
        cortes = cortes.sort(function(a, b){ return b - a })
        var possibilidades = new Array()
        var aux = function(a){
            return checaCortes(max, cortes, a)
        }
        for(var i = 0; i < cortes.length; i++){
            for(var j = 0; j < cortes.length; j++){
                var inc = new Array()
                for(var k = 0; k < j+1; k++){
                    inc.push(cortes[i])
                }
                var vals = [inc, cortes.subList(i,j)].map(function(el){
                    return aux(el)
                })
                for(var k = 0; k < vals.length; k++){
                    possibilidades = possibilidades.concat(vals[k].sort(function(a, b){
                        return b - a
                    }))
                }
            }
        }
        return possibilidades.unique().grep(function(el){
            return el.sum() > max - cortes.min()
        })
    }
    
})

